/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2014年7月4日
 * V4.0
 */
package com.jphenix.driver.threadpool;

import com.jphenix.kernel.baseobject.instanceb.ABase;
import com.jphenix.kernel.objectloader.interfaceclass.IBean;
import com.jphenix.kernel.objectloader.interfaceclass.IBeanRegister;
import com.jphenix.kernel.objectloader.interfaceclass.IBeanRegisterChild;
import com.jphenix.share.lang.SBoolean;
import com.jphenix.share.util.BaseUtil;
import com.jphenix.standard.docs.BeanInfo;
import com.jphenix.standard.docs.ClassInfo;
import com.jphenix.standard.docs.Running;
import com.jphenix.standard.script.IScriptBean;
import com.jphenix.standard.threadpool.ICrontabBean;
import com.jphenix.standard.threadpool.ICrontabService;
import com.jphenix.standard.viewhandler.IViewHandler;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 最新定时格式：
 * 
 * 特点：兼容Linux的Crontab格式，也采纳了Spring定时格式的优点，去掉其缺点。
 * Spring定时格式的缺点：周定义， 1为周日（欧洲标准）。而我们认为1为周一，7为周日（中国，美国，ISO标准）
 *                       复杂，定时时间容易让人产生误解。比如：日期设置为5W，即当月离第五个日期最近的工作
 *                       有可能小于当月第五个日期，有可能大于第五个日期。还有很少用到的关键字C。在设计定时
 *                       字符串时，不知道该用到*还是?（有时用*和用?都行）
 *                       
 * 于是我们本着不能将就用的原则，自己写了个定时任务。
 * 
 * 规则如下：
 * 
 * 
 * 如果分为5段信息
 * * * * * *
 * 则每段含义为： 分 时 日 月 周
 * 
 * 如果分为6段信息
 * 则每段含义为： 秒 分 时 日 月 周
 * 
 * 如果分为7段信息
 * 则每段含义为： 秒 分 时 日 月 周 年
 * 如果需要用到年，必须有秒信息，如果没有用到秒，则用 * 占位
 * 年很少用得到，但开发成本低，一顺手就加上了
 * 
 * 每个分段描述说明：
 * 
 *    *  星号：代表未设置。比如这种情况 * 10 * * *  每天10点执行，但是没设置分值（严格讲，这是错误的），分钟
 *             默认为0
 *             
 *    数值   ：
 *             秒、分钟、小时：0~59   
 *             天            ：1~31（视具体月份而定） 
 *             月            ：1~12 
 *             周            ：0~7（注意：0、7都是周日，用哪个都行） 注意：ISO标准定义7为周日（一个周期最后一天），欧洲周日为1（不采纳）
 *             年            ：当前年份~无限大
 *             
 *    枚举   ：1,3,5,6  适用于 秒、分钟、小时、天、月、周。
 *    
 *    范围   ：1-5      适用于 秒、分钟、小时、天、月、周。
 * 
 *    单词缩写
 *    
 *          月份： Jan   Feb   Mar   Apr   May    Jun    Jul   Aug    Sept   Oct   Nov   Dec （不计大小写）
 *          
 *            周： Mon  Tue   Wed   Thur   Fri   Sat   Sun
 *            
 *    间隔循环： * /1  （注意：星号跟斜杠之间没有空格。这里只能带上空格，否则被认定为注释结束）
 *                      适用于 秒、分钟、小时、天、月、周、年
 *                      比如：用在分钟上，代表每一分钟执行一次 * /5 为每5分钟执行一次
 *                
 *               5 /1   组合信息，如果用在分钟上，代表从第5分钟开始，每分钟执行一次
 *              
 *               5-10 /1 如果用在日期上，意为：从5号到10号，每天执行一次
 *               
 *    末尾关键字L   适用于：天，星期。比如 30 10 5L * *  意为：每月倒数第五天得的10点半执行。
 *                  因为每个月的日期数量是不一样的，用这个关键字就方便多了。比如：30 10 * * 5L
 *                  意为：每个月最后一周的周五的10点半执行。
 *            
 *    末尾关键字W   代表工作日，仅适用于天。比如 * /1W 每个工作日执行一次
 *                                               10W   每月第十个工作日
 *                                               1WL   每个月最后一个工作日
 *                   注意：如果使用到了工作日，需要设置工作日序列或非工作日序列，否则程序无法判断指定日期是否为工作日
 *                   
 *  常用例子：
 *  
 *      30 08 * * *                每天的8点半执行
 *      30 08 * * 1-5              周一到周五，每天8点半执行
 *      30 08 * Jan,Feb,Mar        每一月份、二月份、三月份的每天8点半执行一次
 *      30 08 * * Tue              每周二的8点半执行一次
 *      * /5 08-10 * * *           每天8点到10点之间，每5分钟执行一次
 *      * /3 * * * * *             每三秒执行一次
 *      * /3 * 10 * * *            每天10点，每三秒执行一次
 *      * 00 09 1 1 * * /2         每两年的一月一号9点执行一次                                                                                                                    <br />
 *                                                                                                                                                                   <br />
 *                                                                                                                                                                   <br />
 *  方法1：                                                                                                                                                     <br />
 *               任意受类加载器管理的类，在配置文件中配置一下，即可实现定时任务调用                                 <br />
 *  要求：需要在配置文件中配置
 * <crontab>                                                                                                                                                  <br />
 *      <element id="" method="类主键.调用方法" disabled="1"> * * * * * </element>                                   <br />
 * </crontab>                                                                                                                                                <br />
 *                                                                                                                                                                    <br />
 * 方法2:                                                                                                                                                         <br />
 *             需要实现ICrontabBean接口中指定的方法，不需要在配置文件中配置                                              <br />
 *                                                                                                                                                                    <br />
 *                                                                                                                                                                     <br />
 *                                                                                                                                                
 * 2018-07-04 增加了获取指定定时任务的下一次执行时间
 * 2019-02-01 如果注册进来的是脚本类实例，从脚本类中获取是否输出日志属性
 *                                                                                                                                                                     
 * com.jphenix.driver.threadpool.CrontabService
 * @author 马宝刚
 * 2014年7月4日
 */
@ClassInfo({"2019-02-01 12:59","定时任务服务"})
@BeanInfo({"crontab_service","0","","","1"})
@Running({"90","start","","stop"})
public class CrontabService extends ABase implements ICrontabService,IBeanRegister {

    //定时任务线程
    private List<CrontabThread> threadList = new ArrayList<CrontabThread>();
    //定时任务线程对照容器（防止重复注册）
    private Map<String,CrontabThread> threadMap = new HashMap<String,CrontabThread>();
    private Boolean disabled = null; //是否禁止执行

    
    /**
     * 构造函数
     * @author 马宝刚
     */
    public CrontabService() {
        super();
    }
    
    /**
     * 是否禁止了定时任务服务
     * @return 是否禁止了定时任务服务
     * 2014年10月21日
     * @author 马宝刚
     */
    public boolean hasDisabled() {
        if(disabled==null) {
            disabled =SBoolean.booleanOf(prop.getParameter("crontab/disabled"));
            if(disabled.booleanValue()) {
            	log.startLog("---------------The Conf Set crontab_disabled true, Crontab Service Has Disabled---------------");
                return disabled.booleanValue();
            }
            disabled =SBoolean.booleanOf(prop.getParameter("crontab_disabled"));
            if(disabled.booleanValue()) {
                log.startLog("---------------The Conf Set crontab_disabled true, Crontab Service Has Disabled---------------");
                return disabled.booleanValue();
            }
        }
        return disabled.booleanValue();
    }
    
    /**
     * 获取定时任务线程序列
     * @return 定时任务线程序列
     * 2014年7月5日
     * @author 马宝刚
     */
    @Override
    public List<CrontabThread> getCrontabElement(){
        return threadList;
    }
    
    /**
     * 设置全部暂停执行或者恢复继续执行
     * @param disabled true暂停  false继续执行
     * 2014年7月5日
     * @author 马宝刚
     */
    @Override
    public void setDisabled(boolean disabled) {
        for(CrontabThread ct:threadList) {
            ct.setDisabled(disabled);
        }
    }
    
    /**
     * 停止全部任务
     * 2014年7月5日
     * @author 马宝刚
     */
    @Override
    public void stop() {
        CrontabThread ct; //定时任务线程
        while(threadList.size()>0) {
            ct = threadList.remove(0);
            ct.stopRemove();
        }
    }

    
    /**
     * 启动服务
     * @throws Exception 异常
     * 2014年7月4日
     * @author 马宝刚
     */
    @Override
    public void start() throws Exception {
        if(hasDisabled()) {
            return;
        }
        //配置文件段
        List<IViewHandler> xmls = prop.getParameterXml("crontab").getChildNodesByNodeName("element");
        if(xmls==null) {
            return;
        }
        CrontabVO cvo; //定时任务信息类元素
        String name; //定时任主键
        String cmd; //调用方法名
        String timeInfo = null; //定时信息
        for(IViewHandler xml:xmls) {
            
            name = xml.getAttribute("id");
            cmd = xml.getAttribute("method");
            timeInfo = xml.nt();
            
            if(SBoolean.valueOf(xml.getAttribute("disabled"))) {
                log.startLog("Init The Crontab id:["+name+"] method:["+cmd+"] value:["+timeInfo+"] Has Disabled");
                continue;
            }
            cvo = new CrontabVO(this);
            if(!cvo.setInfo(name,timeInfo,cmd, null)) {
                //log.warning("Init The Crontab id:["+name+"] method:["+cmd+"] value:["+timeInfo+"] Error",null);
                continue;
            }
            //构建线程
            CrontabThread ct = new CrontabThread(cvo);
            //放入容器中
            threadMap.put(name,ct);
            //放入序列中
            threadList.add(ct);
        }
    }
    

    /**
     * 获取指定定时任务类上次执行时间
     * @param beanId 定时任务类主键
     * @return 上次执行时间
     * 2016年5月9日
     * @author MBG
     */
    @Override
    public String getLastExecuteDate(String beanId) {
    	if(!threadMap.containsKey(beanId)) {
    		return "";
    	}
    	//获取指定定时任务
    	CrontabThread ct = threadMap.get(beanId);
    	return ct.getCrontabVO().getLastExecuteDate();
    }
    
    
    /**
     * 获取指定定时任务的下次执行时间
     * @param beanId 定时任务类主键
     * @return 下次执行时间
     * 2018年7月4日
     * @author MBG
     */
    @Override
    public String getNextExecuteDate(String beanId) {
    	if(!threadMap.containsKey(beanId)) {
    		return "";
    	}
    	//获取指定定时任务
    	CrontabThread ct = threadMap.get(beanId);
    	return ct.getCrontabVO().getNextExecuteDate();
    }
    
    /**
     * 指定任务是否正在运行中
     * @param beanId 任务类主键
     * @return 是否在运行中
     * 2016年5月26日
     * @author MBG
     */
    @Override
    public boolean isRunning(String beanId) {
    	if(!threadMap.containsKey(beanId)) {
    		return false;
    	}
    	//获取指定定时任务
    	CrontabThread ct = threadMap.get(beanId);
    	
    	return ct.getCrontabVO().isRunning();
    }
    
    /**
     * 获取受控的定时任务类主键序列
     * @return 受控的定时任务类主键序列
     * 2016年5月9日
     * @author MBG
     */
    @Override
    public List<String> getCrontabIdList(){
    	return BaseUtil.getMapKeyList(threadMap);
    }
    
    
    /**
     * 覆盖方法
     */
    @SuppressWarnings("deprecation")
    @Override
    public boolean regist(Object bean) {
        if(hasDisabled()) {
            return true;
        }
        if(bean==null || !(bean instanceof ICrontabBean)) {
            return false;
        }
        
        String beanId = ((IBean)bean).getBeanID(); //类主键
        String timerInfo = ((ICrontabBean)bean).getTimeInfo(); //时间信息
        CrontabThread ct = null; //线程
        
        if(threadMap.containsKey(beanId)) {
            //已经注册过
            ct = threadMap.get(beanId);
            ct.stop();
            threadList.remove(ct);
            threadMap.remove(beanId);
        }
        //定时任务信息类元素
        CrontabVO cvo = new CrontabVO(this);
        if(bean instanceof IScriptBean) {
        	cvo.noOutLog = ((IScriptBean)bean).noOutLog();
        }
        if(cvo.setInfo(beanId,timerInfo,(ICrontabBean)bean)) {
            log.startLog("Crontab: LoadBean:["+beanId+"] Timer:["+timerInfo+"]");
        }else {
            //log.warning("Init The Crontab LoadBean:["+beanId+"] TimeInfo:["+timerInfo+"] Error",null);
        }
        ct = new CrontabThread(cvo);
        //放入对照容器中
        threadMap.put(beanId,ct);
        //放入序列中
        threadList.add(ct);
        
        if(bean instanceof IBeanRegisterChild) {
            ((IBeanRegisterChild)bean).setRegister(this);
            ((IBeanRegisterChild)bean).afterRegister();
        }
        return true;
    }
    
    /**
     * 覆盖方法
     */
    @SuppressWarnings("deprecation")
    @Override
    public void unRegist(Object bean) {
        if(bean==null || !(bean instanceof ICrontabBean)) {
            return;
        }
        if(bean instanceof IBeanRegisterChild) {
            ((IBeanRegisterChild)bean).beforeUnRegister();
        }
        String beanId = ((IBean)bean).getBeanID(); //类主键
        CrontabThread ct = null; //线程
        
        if(threadMap.containsKey(beanId)) {
            //已经注册过
            log("---------------------------------The Crontab Bean Has UnRegist:["+beanId+"]");
            ct = threadMap.get(beanId);
            ct.stop();
            threadList.remove(ct);
            threadMap.remove(beanId);
        }
    }
}
